module.exports = {


  friendlyName: 'Get remediation timeline',


  description: 'Get snapshots of progress toward resolving the specified vulnerability.',


  inputs: {

    vulnerabilityId: {
      description: 'The vulnerability whose resolution progress we\'re examining.',
      type: 'number',
      required: true,
    },

    teamApid: {
      description: 'The ID of the Team to filter by, or 0 to only include hosts with no team, or undefined to not filter by any team.',
      type: 'number',
    }

  },


  exits: {

    success: {
      outputType: [{}],
    },
  },


  fn: async function ({vulnerabilityId, teamApid}) {

    // Find the database record for this vulnerability, populating hosts and installs
    let vulnerabilityRecord = await Vulnerability.findOne({id: vulnerabilityId}).populate('hosts').populate('installs');
    let vulnInstalls = vulnerabilityRecord.installs;
    // Create a filtered array of currently installed vulnerable software
    let currentVulnInstalls = _.filter(vulnerabilityRecord.installs, (install)=>{
      return install.uninstalledAt === 0;
    });
    // Create a filtered array of the hosts with software affected by this vulnerability currently installed to use in the remediation timeline modal.
    // Filtering out hosts that have vulnerable software currently installed.
    let affectedHosts = _.filter(vulnerabilityRecord.hosts, (host)=>{
      return _.contains(_.pluck(currentVulnInstalls, 'host'), host.id);
    });

    // If we're filtering results to a specific team, we'll filter the results to only track vulnerable installs/uninstalls for hosts on the selected team.
    if(teamApid !== undefined) {
      let affectedFilteredHosts = _.filter(affectedHosts, { 'teamApid': teamApid });
      affectedHosts = affectedFilteredHosts;
      let hostIdsInThisTeam = _.pluck(_.filter(vulnerabilityRecord.hosts, { 'teamApid': teamApid }), 'id');
      vulnInstalls = vulnInstalls.filter((install)=>{
        return _.contains(hostIdsInThisTeam, install.host);
      });
    }

    // Add an array containing information about every software item affected by this vulnerability a host has installed.
    // We'll use this to show every vulnerable software affected by this vulnerability
    for(let host of affectedHosts){
      host.installsAffectedByThisVulnerability = [];
      let installsForThisHost = _.filter(currentVulnInstalls, (install)=>{
        return host.id === install.host && install.uninstalledAt === 0;
      });
      for(let install of installsForThisHost){
        host.installsAffectedByThisVulnerability.push({
          softwareNameAndVersion: `${install.softwareName} @ ${install.versionName}`, // Build a string containing the software name and version name for this affected software.
          fleetApid: install.fleetApid, // Add the fleetApid so we can build a link to this software item in the Fleet instance.
        });
      }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Build the remediation timeline for this vulnerability.
    // We'll do this by building an array of objects representing all changes to software installations affected by this vulnerability
    // e.g., [{ timestamp: 1689271300000, change: 1, host: 5 },{ timestamp: 1689087600000, change: 1, host: 6 },{ timestamp: 1689087600000, change: 1, host: 12 }]
    //
    // We'll then group the changes by timestamp and track the number of hosts affected by this vulnerability of time.
    // Note: because a vulnerability can affect multiple software versions, and hosts be affected by the same vulnerability multiple times, we'll only track software being installed/uninstalled
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



    // Create an array that we will use to track a remediation timeline for all software affected by this vulnerability.
    let installChangesOverTime = [];
    let totalNumberOfVulnInstallsByHost = {};

    // For each software install affected by this vulnerability, we'll add up to two objects to the installChangesOverTime array.
    for(let install of vulnInstalls) {
      // Add an object with the timestamp of when this software was detected with a change of 1 for this host.
      installChangesOverTime.push({changedAt: install.installedAt, change: 1, host: install.host});
      // If this software was uninstalled, add an object with the uninstalledAt timestamp and a change of -1.
      if(install.uninstalledAt !== 0){
        installChangesOverTime.push({changedAt: install.uninstalledAt, change: -1, host: install.host});
      }
      // Add this host to the totalNumberOfVulnInstallsByHost dictionary, this number will be incremented/subtracted when we iterate through the individual changes to software affected by this vulnerability.
      totalNumberOfVulnInstallsByHost[install.host] = 0;
    }//∞
    // Instead of adding individual points on the graph for each software install/uninstall that share the same timestamp,
    // we'll group the changes by changedAt values (rounded to the nearest 30 minutes), and add graphpoints representing the sum of the changes.
    // The created object will look something like this: { '1689038373501': [ { changedAt: 1689038373501, change: 1 }, {} ], 1682300599332: [{...}] }

    let changesSortedAndGroupedByChangedAt = _.groupBy(_.sortBy(installChangesOverTime, 'changedAt'), (change)=>{
      // Round the changedAt values to the nearest 30 minutes and group them by that value.
      return Math.round(change.changedAt / (30 * 60 * 1000)) * (30 * 60 * 1000);
    });


    let timeline = [];// « this is for our graph
    let lastNumberOfAffectedHosts = 0;// « Set a variable to keep track of the number of hosts affected in the last graphpoint added.
    // console.log(changesSortedAndGroupedByChangedAt);
    // Now build our timeline.
    // > Iterate through the top-level objects in the changesSortedAndGroupedByChangedAt object
    for(let groupOfChanges in changesSortedAndGroupedByChangedAt) {
      // console.log(groupOfChanges);
      let timestampForThisGroup;
      // Iterate through the array of grouped changes, applying the changes in affected hosts to totalNumberOfVulnInstallsByHost
      for(let softwareChange of changesSortedAndGroupedByChangedAt[groupOfChanges]) {
        totalNumberOfVulnInstallsByHost[softwareChange.host] += softwareChange.change;
        timestampForThisGroup = softwareChange.changedAt;
      }//∞

      // Get the numebr of affected hosts during this time.
      let affectedHostsDuringThisTime = _.filter(totalNumberOfVulnInstallsByHost, (installCount)=>{return installCount > 0;}).length;
      // If the number of affected hosts is the same, we won't add a graph point (i.e., An affected host installs software that is affected by a vulnerability that is already present on the host)
      if(affectedHostsDuringThisTime !== lastNumberOfAffectedHosts){
        // create a graphpoint tracking the timestamp and the affectedHostsDuringThisTime
        let graphPoint = {
          timestamp: timestampForThisGroup,
          numAffectedHosts: affectedHostsDuringThisTime,
        };
        // Add a graphpoint to the timeline for this software
        timeline.push(graphPoint);
      }
      lastNumberOfAffectedHosts = affectedHostsDuringThisTime;
    }//∞

    // If this vulnerability currently still affects any hosts, we'll add a timeline object with the current number of affected hosts.
    if(timeline.length >= 1 && timeline[timeline.length - 1].numAffectedHosts > 0) {
      timeline.push({
        timestamp: Date.now(),
        numAffectedHosts: timeline[timeline.length - 1].numAffectedHosts,
      });
    }

    let timelineForThisVulnerability = _.sortBy(timeline, 'timestamp');

    return {
      timelineForThisVulnerability,
      affectedHosts
    };

  }


};
